package top.lingkang.mm.orm;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.lingkang.mm.annotation.Id;
import top.lingkang.mm.constant.IdType;
import top.lingkang.mm.error.MagicException;
import top.lingkang.mm.gen.DefaultIdGenerate;
import top.lingkang.mm.gen.IdGenerate;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lingkang
 * Created by 2024/3/4
 */
class BaseMapperManage {
    private static final Logger log = LoggerFactory.getLogger(BaseMapperManage.class);
    public Configuration configuration;
    public SqlSession sqlSession;
    public IdGenerate idGenerate = new DefaultIdGenerate();

    public BaseMapperManage(Configuration configuration, SqlSession sqlSession) {
        Assert.notNull(sqlSession, "sqlSession 不能为空");
        if (sqlSession instanceof DefaultSqlSession)
            throw new MagicException("创建 BaseMapperManage 的 sqlSession 不能是: " + sqlSession.getClass().getName());
        this.configuration = configuration;
        this.sqlSession = sqlSession;
        template = IoUtil.read(
                getClass().getClassLoader().getResourceAsStream("MagicTemplate.xml"),
                StandardCharsets.UTF_8);

        // 加载lang
        configuration.addMapper(MagicCreateLangMapper.class);
        langMapper = configuration.getMapper(MagicCreateLangMapper.class, sqlSession);
    }

    private final String template;
    public final MagicCreateLangMapper langMapper;

    private final Map<Class<?>, MagicEntity> cache = new HashMap<>();

    public boolean isLoad(Class<?> clazz) {
        return cache.containsKey(clazz);
    }

    public MagicEntity getMagicEntity(Class<?> clazz) {
        MagicEntity entity = cache.get(clazz);
        if (entity != null)
            return entity;
        entity = MagicEntityUtils.getMagicEntity(clazz);

        // 缓存
        cache.put(clazz, entity);

        // load template
        loadTemplate(entity);

        return entity;
    }

    protected void loadTemplate(MagicEntity entity) {
        int idIndex = entity.getIdIndex();

        String temp = template
                // select
                .replace("@index_", entity.getClazz().getName())
                .replace("@table_", entity.getTableName())
                .replace("@columns_", MagicEntityUtils.getColumns(entity.getColumnName(), null))
                .replace("@entityType_", entity.getClazz().getName())

                // insert
                .replace("@id_", idIndex == -1 ? "_" : entity.getColumnName().get(idIndex))
                .replace("@values_", getInsertValues(entity))
                .replace("@sequence_values", getInsertValuesSequence(entity))
                .replace("@columnsE_", MagicEntityUtils.getInsertPrefixValues(entity, "e"))

                // update
                .replace("@setColumns_", MagicEntityUtils.getSetColumns(entity, null));

        // 对id生成进行调整
        if (entity.getIdAnn() != null && entity.getIdAnn().value() != IdType.AUTO) {
            temp = temp.replace("useGeneratedKeys=\"true\"", "")
                    .replace("keyProperty=\"" + entity.getFields().get(entity.getIdIndex()).getName() + "\"", "")
                    .replace("keyProperty=\"e." + entity.getFields().get(entity.getIdIndex()).getName() + "\"", "");
        }


        // log.debug(temp);
        InputStream in = IoUtil.toStream(temp, StandardCharsets.UTF_8);
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(in, configuration, entity.getClazz().getName(),
                configuration.getSqlFragments());
        mapperParser.parse();
        log.debug("{} --> {}", entity.getClazz().getName(), temp);
    }

    private String getInsertValues(MagicEntity entity) {
        List<String> col = entity.getColumnName();
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < col.size(); i++) {
            res.append("#{").append(col.get(i)).append("}");
            if (i < col.size() - 1)
                res.append(",");
        }
        return res.toString();
    }

    private String getInsertValuesSequence(MagicEntity entity) {
        boolean isSequence = false;
        if (entity.getIdAnn() != null && entity.getIdAnn().value() == IdType.AUTO && StrUtil.isNotEmpty(entity.getIdAnn().sequence())) {
            isSequence = true;
        }
        List<String> col = entity.getColumnName();
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < col.size(); i++) {
            if (isSequence && i == entity.getIdIndex()) {
                res.append("nextval('").append(entity.getIdAnn().sequence()).append("')");
            } else
                res.append("#{").append(col.get(i)).append("}");
            if (i < col.size() - 1)
                res.append(",");
        }
        return res.toString();
    }

    // --------------------------------------------------------------------------------------

    public Map<String, Object> getUpdateNotNullParams(Object obj, MagicEntity entity) {
        Map<String, Object> param = new HashMap<>();
        StringBuilder columns = new StringBuilder();
        for (int i = 0; i < entity.getColumnName().size(); i++) {
            try {
                Field field = entity.getFields().get(i);
                if (field.get(obj) != null) {
                    columns.append(entity.getColumnName().get(i)).append("=#{e.")
                            .append(entity.getFields().get(i).getName()).append("}, ");
                } else if (i == entity.getIdIndex())
                    throw new MagicException("更新对象的 " + field.getName() + " 不能为空值: " + obj);
            } catch (Exception e) {
                throw new MagicException(e);
            }
        }
        if (columns.length() == 0)
            throw new MagicException("不能更新全空值：" + obj);
        param.put("columns", columns.substring(0, columns.length() - 2));
        param.put("e", obj);
        return param;
    }

    public String getSqlId(Class<?> clazz, String action) {
        MagicEntity entity = getMagicEntity(clazz);
        return getSqlId(entity, action);
    }

    public String getSqlId(MagicEntity entity, String action) {
        return "magic_" + entity.getClazz().getName() + "." + action;
    }

    public String getQtId(String id) {
        return "magicQuery." + id;
    }

    public void checkIdSet(MagicEntity entity, Object obj, IdGenerate idGenerate) {
        Id id = entity.getIdAnn();
        if (id != null) {
            if (id.value() == IdType.ASSIGN) {
                String idName = entity.getFields().get(entity.getIdIndex()).getName();
                try {
                    Field field = entity.getFields().get(entity.getIdIndex());
                    Object oId = field.get(obj);
                    if (oId != null)
                        return;
                    // 自动生成 id
                    Object genId = idGenerate.nextId(id.assignParam());
                    if (field.getType() != genId.getClass()) {
                        throw new MagicException("IdGenerate 自动生成 id 类型: " + genId.getClass().getName() +
                                "  与实体类 id 类型不一致: " + field.getType().getName() +
                                "  实体类: " + entity.getClazz().getName());
                    }
                    field.set(obj, genId);
                } catch (Exception e) {
                    throw new MagicException(e);
                }
            }
        }
    }

    protected Object getIdValue(Object obj, Class<?> clazz, String name) {
        try {
            String metName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
            Method method = clazz.getDeclaredMethod(metName);
            return method.invoke(obj);
        } catch (Exception e) {
        }
        try {
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
        }
        return null;
    }

    // get 、 set
    public IdGenerate getIdGenerate() {
        return idGenerate;
    }

    public void setIdGenerate(IdGenerate idGenerate) {
        this.idGenerate = idGenerate;
    }
}
